//    Travel US - For users of travel.usairways.com
//    intended for US Airways employees and their dependents
//    This app will help you track your flights and display
//    Availability, flight status and other non-rev info
//    Copyright (C) 2014  Renats Stozkovs
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

package com.bc004346.travelus.DataAccess;

import android.util.Log;

import com.bc004346.travelus.Model.Domain.Flight;
import com.bc004346.travelus.Model.Domain.Passenger;
import com.bc004346.travelus.Model.Domain.Reservation;
import com.bc004346.travelus.Model.TravelUsException;
import com.bc004346.travelus.Model.Tuple;

import org.apache.commons.lang3.StringEscapeUtils;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import static com.bc004346.travelus.Utility.DATE;
import static com.bc004346.travelus.Utility.DAY;
import static com.bc004346.travelus.Utility.LONG_DATE;
import static com.bc004346.travelus.Utility.MINUTE;
import static com.bc004346.travelus.Utility.SHORT_DATE;
import static com.bc004346.travelus.Utility.SHORT_TIME;
import static com.bc004346.travelus.Utility.log;

/**
 * Created by Renats on 1/20/14.
 * This is a data access screen scraper against
 * Travel US website
 */
public class TravelUsScreenScraper {

    //<editor-fold desc="Members">

    private static final String PNR_DETAILS_PASSENGERS = "pnrDetailsForm:j_id119";
    private static final String PNR_DETAILS_ITINERARY = "pnrDetailsForm:itins";
    private static final String PNR_TABLE = "table[class=iceDatTblScrlBdyTbl]";
    private static final String CHECKIN_TABLE = "table[class=iceDatTblScrlBdyTbl]";
    private static final String DATA_TABLE_ROW = "tr[class^=iceDatTblRow]";
    private static final String PBT_DATA_TABLE_ROW = "tr[class^=icePnlGrdRow]";
    private static final String CHECK_PBT_FORM_AVAILABILITY = "checkPbtForm:j_id69:0:j_id83";
    private static final String CHECK_PBT_FORM_NON_REVS = "checkPbtForm:j_id69:0:j_id108";
    private static final String CHECKED_IN_SUCCESS = "checkinConfirmation.jsf";
    private static final String USER_AGENT = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.3; WOW64; Trident/7.0)";
    private static final String SPAN = "span";
    private static final String IFRAME = "iframe";
    private static final String CONTENT = "content";
    private static final String TD = "td";
    private static final String FLIGHT_SEARCH_ERROR = "[address$=Error";
    private static final String UPDATE = "UPDATE";

    private static final String URI_PROFILE = "https://travel.usairways.com/us/search/profile.jsf";
    private static final String URI_CHECK_PBT = "https://travel.usairways.com/us/search/checkPbt.jsf";
    private static final String URI_CHECK_IN = "https://travel.usairways.com/us/checkin/checkin.jsf";
    private static final String URI_ITINERARY = "https://travel.usairways.com/us/search/itinerary.jsf";
    private static final String URI_PBT_TRACKER = "https://travel.usairways.com/us/search/pbtTracker.jsf";
    private static final String URI_PNR_DETAILS = "https://travel.usairways.com/us/search/pnrDetails.jsf";
    private static final String URI_SECURITY_CHECK = "https://travel.usairways.com/us/search/j_security_check";
    private static final String URI_SEND_RECEIVE_UPDATES = "https://travel.usairways.com/us/block/send-receive-updates";
    private static final String URI_CHECKIN_CONFIRMATION = "https://travel.usairways.com/us/checkin/checkin.jsf?boardPoint=%s&recLoc=%s&badgeNo=%s&companyCode=US";

    private static final String LABEL_BOARDING_PRIORITY = "Boarding Priority";
    private static final String LABEL_FIRST = "First";
    private static final String LABEL_TOTAL_CAPACITY = "Total Capacity";
    private static final String LABEL_AUTHORIZED = "Authorized";
    private static final String LABEL_BOOKED = "Booked";
    private static final String LABEL_SPACE_AVAILABLE = "Space Available";
    private static final String LABEL_NON_REVS_LISTED = "Non Revs Listed";
    private static final String FIELD_BADGE = "profileForm:j_id145";
    private static final String FIELD_SENIORITY = "profileForm:j_id148";
    private static final String FIELD_NAME = "profileForm:j_id142";

    private String sessionId = "";
    private String profileSession = "";
    private String name = "";
    private String badge = "0";
    private Date seniority = new Date();
    private String mUsername = "";
    private String mPassword = "";
    private Boolean isLoggedIn = false;

    //</editor-fold>
    //<editor-fold desc="Constructor">

    public TravelUsScreenScraper(String name, String badge, Date seniority) {
        setName(name);
        setBadge(badge);
        setSeniority(seniority);
    }

    //</editor-fold>
    //<editor-fold desc="Properties">

    private static void trustAllHosts() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {

            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {

            }

            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[]{};
            }
        }};

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection
                    .setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private int getTimeout() {
        return 60000;
    }

    public Date getSeniority() {
        return seniority;
    }

    public void setSeniority(Date seniority) {
        this.seniority = seniority;
    }

    public String getBadge() {
        return badge;
    }

    public void setBadge(String badge) {
        this.badge = badge;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //</editor-fold>
    //<editor-fold desc="Public Methods">

    public Boolean getIsLoggedIn() {
        return isLoggedIn;
    }

    public Boolean doLogin(String username, String password) throws Exception {

        log("starting...");

        mUsername = username;
        mPassword = password;

        trustAllHosts();
        Connection.Response res = Jsoup.connect(URI_ITINERARY)
                .userAgent(USER_AGENT)
                .timeout(getTimeout())
                .ignoreHttpErrors(true)
                .followRedirects(true)
                .execute();

        Document doc = res.parse();
        String csrf = doc.select("input").get(6).val();
        sessionId = res.cookie("JSESSIONID");

        res = Jsoup.connect(URI_SECURITY_CHECK).timeout(getTimeout())
                .userAgent(USER_AGENT).data("j_username", username)
                .data("j_password", password).data("csrf", csrf)
                .cookie("JSESSIONID", sessionId).referrer(URI_ITINERARY)
                .method(Connection.Method.POST).execute();
        doc = res.parse();
        sessionId = res.cookie("JSESSIONID");

        Boolean result = !doc.text().contains("You have entered an incorrect user name or password");

        isLoggedIn = result;
        log(String.format("end... returning %s", result));

        return result;
    }

    public List<Reservation> getProfile(int daysToSearch) throws Exception {

        log("starting...");

        List<Reservation> reservations = new ArrayList<Reservation>();
        String pnr;
        Date date;
        String oldPnr;
        String bookingType;
        Reservation res;

        Document doc = Jsoup.connect(URI_PROFILE).referrer(URI_ITINERARY)
                .cookie("JSESSIONID", sessionId).userAgent(USER_AGENT)
                .timeout(getTimeout()).get();

        if (doc.text().contains("Please enter your user name and password that you use for Wings")) {
            if (!doLogin(mUsername, mPassword))
                throw new TravelUsException("Unable to login");
            else {
                doc = Jsoup.connect(URI_PROFILE).referrer(URI_ITINERARY)
                        .cookie("JSESSIONID", sessionId).userAgent(USER_AGENT)
                        .timeout(getTimeout()).get();
            }
        }

        ParseUserData(doc);

        Element table = doc.select(PNR_TABLE).get(1);
        profileSession = doc.select(IFRAME).first().id().substring(14, 36);
        Iterator<Element> iterator = table.select(TD).iterator();
        while (iterator.hasNext()) {
            pnr = iterator.next().text();
            date = DATE.parse(iterator.next().text());
            bookingType = iterator.next().text();
            iterator.next();
            oldPnr = iterator.next().html();
            res = new Reservation(pnr, date, bookingType);

            if (Math.abs((res.getDeparture().getTime() - new Date().getTime()) / DAY) <= daysToSearch
                    && oldPnr.length() == 0)
                reservations.add(res);
        }

        log(String.format("end... returning %d reservations(s)", reservations.size()));

        return reservations;
    }

    public Tuple<List<Flight>, List<Passenger>> getReservationDetails(
            String pnr) throws IOException, ParseException {

        log("starting...");

        Jsoup.connect(URI_SEND_RECEIVE_UPDATES).referrer(URI_PROFILE)
                .cookie("JSESSIONID", sessionId)
                .cookie("ice.sessions", profileSession + "#1").userAgent(USER_AGENT)
                .data("ice.submit.partial", "false")
                .data("ice.event.target", "profileForm:listings:0:listPnr")
                .data("ice.event.captured", "profileForm:listings:0:listPnr")
                .data("ice.event.type", "onclick")
                .data("ice.event.alt", "false").data("ice.event.ctrl", "false")
                .data("ice.event.shift", "false")
                .data("ice.event.meta", "undefined").data("ice.event.x", "590")
                .data("ice.event.y", "402").data("ice.event.left", "false")
                .data("ice.event.right", "false")
                .data("profileForm", "profileForm")
                .data("icefacesCssUpdates", "")
                .data("javax.faces.ViewState", "1")
                .data("javax.faces.RenderKitId", "ICEfacesRenderKit")
                .data("profileForm:phoneNum", "")
                .data("profileForm:emailAddress", "")
                .data("profileForm:eligibleFirstNameHidden", "")
                .data("profileForm:eligibleLastNameHidden", "")
                .data("profileForm:eligibleIdHidden", "")
                .data("profileForm:eligiblePassword", "")
                .data("profileForm:eligiblePasswordConfirm", "")
                .data("profileForm:to", "")
                .data("profileForm:loginActionHidden", "")
                .data("profileForm:j_id135Expanded", "")
                .data("profileForm:recLookup", "")
                .data("profileForm:j_id183Expanded", "")
                .data("profileForm:j_id208Expanded", "")
                .data("profileForm:_idcl", "profileForm:listings:0:listPnr")
                .data("recloc", pnr).data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "profileForm:listings:0:listPnr")
                .data("rand", getRandom()).timeout(getTimeout())
                .method(Connection.Method.POST).execute();

        Document doc = Jsoup.connect(URI_PNR_DETAILS).referrer(URI_PROFILE)
                .cookie("JSESSIONID", sessionId).userAgent(USER_AGENT)
                .timeout(getTimeout()).get();

        Element table = doc.getElementById(PNR_DETAILS_ITINERARY);
        List<Flight> flights = getFlights(table, pnr);

        table = doc.getElementById(PNR_DETAILS_PASSENGERS);
        List<Passenger> pax = getPassengers(table);

        Jsoup.connect(URI_PROFILE).referrer(URI_ITINERARY)
                .cookie("JSESSIONID", sessionId).userAgent(USER_AGENT)
                .timeout(getTimeout()).get();

        log(String.format("end... returning %d flight(s) and %d passenger(s)", flights.size(), pax.size()));

        return new Tuple<List<Flight>, List<Passenger>>(flights, pax);
    }

    public Boolean doCheckin(String badge, String pnr, String origin) throws Exception {

        log(String.format("starting... (origin = %s)", origin));

        Document doc = Jsoup.connect(URI_CHECK_IN).referrer(URI_ITINERARY)
                .cookie("JSESSIONID", sessionId).cookie("ice.sessions", "")
                .timeout(getTimeout()).get();

        if (doc.text().contains("Please enter your user name and password that you use for Wings")) {
            if (!doLogin(mUsername, mPassword))
                throw new TravelUsException("Unable to login");
            else {

                doc = Jsoup.connect(URI_CHECK_IN).referrer(URI_ITINERARY)
                        .cookie("JSESSIONID", sessionId).cookie("ice.sessions", "")
                        .timeout(getTimeout()).get();
            }
        }
        String session = doc.select(IFRAME).first().id().substring(14, 36);

        doc = Jsoup.connect(URI_SEND_RECEIVE_UPDATES).referrer(URI_CHECK_IN)
                .cookie("JSESSIONID", sessionId)
                .cookie("ice.sessions", session + "#1")
                .userAgent(USER_AGENT).data("ice.submit.partial", "false")
                .data("ice.event.target", "checkinForm:btnLookup")
                .data("ice.event.captured", "checkinForm:btnLookup")
                .data("ice.event.type", "onclick")
                .data("ice.event.alt", "false")
                .data("ice.event.ctrl", "false")
                .data("ice.event.shift", "false")
                .data("ice.event.meta", "undefined")
                .data("ice.event.x", "252").data("ice.event.y", "307")
                .data("ice.event.left", "false")
                .data("ice.event.right", "false")
                .data("checkinForm:btnLookup", "Submit")
                .data("checkinForm", "").data("icefacesCssUpdates", "")
                .data("javax.faces.ViewState", "1")
                .data("javax.faces.RenderKitId", "")
                .data("checkinForm:selCode", "US")
                .data("checkinForm:badgeNum", badge)
                .data("checkinForm:confirmCd", pnr)
                .data("checkinForm:board", origin)
                .data("checkinForm:board_idx", "")
                .data("ice.session", session)
                .data("ice.view", "1")
                .data("ice.focus", "checkinForm:btnLookup")
                .data("rand", getRandom())
                .timeout(getTimeout()).post();

        doc = extractInnerHTML(doc, 2);
        if (doc == null) {
            log("Unable to extract inner HTML");
            return false;
        }

        Element table = doc.select(CHECKIN_TABLE).get(0);
        int pax = table.select(SPAN).size();

        Connection conn = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(String.format(URI_CHECKIN_CONFIRMATION, origin, pnr, badge))
                .cookie("JSESSIONID", sessionId)
                .cookie("ice.sessions", session + "#1")
                .userAgent(USER_AGENT)
                .data("ice.submit.partial", "false")
                .data("ice.event.target", "checkinForm:btnConfirm")
                .data("ice.event.captured", "checkinForm:btnConfirm")
                .data("ice.event.type", "onclick")
                .data("ice.event.alt", "false")
                .data("ice.event.ctrl", "false")
                .data("ice.event.shift", "false")
                .data("ice.event.meta", "undefined")
                .data("ice.event.x", "252").data("ice.event.y", "307")
                .data("ice.event.left", "false")
                .data("ice.event.right", "false")
                .data("checkinForm:btnConfirm", "Check In")
                .data("checkinForm", "")
                .data("icefacesCssUpdates", "")
                .data("javax.faces.ViewState", "1")
                .data("javax.faces.RenderKitId", "")
                .data("checkinForm:selCode", "US")
                .data("checkinForm:badgeNum", badge)
                .data("checkinForm:confirmCd", pnr)
                .data("checkinForm:board", origin)
                .data("checkinForm:board_idx", "");
        for (int i = 0; i < pax; i++)
            conn.data(String.format("checkinForm:j_id59:%d:j_id63", i), "on");

        doc = conn.data("ice.session", session)
                .data("ice.view", "1")
                .data("ice.focus", "checkinForm:btnLookup")
                .data("rand", getRandom()).timeout(getTimeout()).post();

        Boolean returnValue = doc.html().contains(CHECKED_IN_SUCCESS);
        if (!returnValue)
            log(doc.html());

        log(String.format("end... returning %s", returnValue.toString()));
        return returnValue;
    }

    public Flight checkAvailability(Flight flight) throws Exception {

        log(String.format("start... (Flight = %s)", flight));

        Document doc = Jsoup.connect(URI_CHECK_PBT).referrer(URI_ITINERARY)
                .cookie("JSESSIONID", sessionId).cookie("ice.sessions", "")
                .timeout(getTimeout()).get();
        String session = doc.select(IFRAME).first().id().substring(14, 36);

        if (doc.text().contains("Please enter your user name and password that you use for Wings")) {
            if (!doLogin(mUsername, mPassword))
                throw new TravelUsException("Unable to login");
            else {

                doc = Jsoup.connect(URI_CHECK_PBT).referrer(URI_ITINERARY)
                        .cookie("JSESSIONID", sessionId).cookie("ice.sessions", "")
                        .timeout(getTimeout()).get();
                session = doc.select(IFRAME).first().id().substring(14, 36);
            }
        }

        doc = Jsoup.connect(URI_SEND_RECEIVE_UPDATES).referrer(URI_CHECK_PBT)
                .cookie("JSESSIONID", sessionId)
                .cookie("ice.sessions", session + "#1")
                .userAgent(USER_AGENT)
                .data("ice.submit.partial", "false")
                .data("ice.event.target", "checkPbtForm:search")
                .data("ice.event.captured", "checkPbtForm:search")
                .data("ice.event.type", "onclick")
                .data("ice.event.alt", "false")
                .data("ice.event.ctrl", "false")
                .data("ice.event.shift", "false")
                .data("ice.event.meta", "undefined")
                .data("ice.event.x", "287")
                .data("ice.event.y", "563")
                .data("ice.event.left", "false")
                .data("ice.event.right", "false")
                .data("checkPbtForm:search", "Get PBT Detail")
                .data("checkPbtForm", "")
                .data("icefacesCssUpdates", "")
                .data("javax.faces.ViewState", "1")
                .data("javax.faces.RenderKitId", "")
                .data("checkPbtForm:flightNumber", String.valueOf(flight.getFlightNumber()))
                .data("checkPbtForm:board", flight.getOrigin())
                .data("checkPbtForm:board_idx", "")
                .data("checkPbtForm:dest", flight.getDestination())
                .data("checkPbtForm:dest_idx", "")
                .data("checkPbtForm:deptdate", SHORT_DATE.format(flight.getDeparture()))
                .data("checkPbtForm:_idcl", "checkPbtForm:j_id69:0:j_id103")
                .data("checkPbtForm:_idcheckPbtForm:deptdatesp", "")
                .data("ice.session", session)
                .data("ice.view", "1")
                .data("ice.focus", "checkPbtForm:j_id69:0:j_id103")
                .data("rand", getRandom())
                .timeout(getTimeout()).post();

        flight = parseAvailability(flight, doc);

        log("end...");

        return flight;
    }

    public List<Flight> searchFlights(String origin, String destination, Date departureDate,
                                      Date returnDate, Boolean isOneWaySearch) throws Exception {

        log("starting...");

        List<Flight> flights = new ArrayList<Flight>();

        Document login = Jsoup.connect(URI_ITINERARY)
                .referrer(URI_PROFILE)
                .cookie("JSESSIONID", sessionId)
                .userAgent(USER_AGENT)
                .timeout(getTimeout()).get();

        profileSession = login.select(IFRAME).first().id().substring(14, 36);

        Document doc = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_ITINERARY)
                .userAgent(USER_AGENT)
                .cookie("JSESSIONID", sessionId)
                .cookie("ice.sessions", profileSession + "#1")
                .data("ice.submit.partial", "false")
                .data("ice.event.target", "itineraryForm:search")
                .data("ice.event.captured", "itineraryForm:search")
                .data("ice.event.type", "onclick")
                .data("ice.event.alt", "false")
                .data("ice.event.ctrl", "false")
                .data("ice.event.shift", "false")
                .data("ice.event.meta", "undefined")
                .data("ice.event.x", "163")
                .data("ice.event.y", "275")
                .data("ice.event.left", "false")
                .data("ice.event.right", "false")
                .data("itineraryForm:search", "Search")
                .data("itineraryForm", "")
                .data("icefacesCssUpdates", "")
                .data("javax.faces.ViewState", "1")
                .data("javax.faces.RenderKitId", "")
                .data("itineraryForm:bookingType", "SA")
                .data("itineraryForm:tripType", isOneWaySearch ? "OneWay" : "TwoWay")
                .data("itineraryForm:board", origin)
                .data("itineraryForm:board_idx", "")
                .data("itineraryForm:dest", destination)
                .data("itineraryForm:dest_idx", "")
                .data("itineraryForm:dd", DATE.format(departureDate))
                .data("itineraryForm:j_id50", "ANY")
                .data("itineraryForm:_idcl", "")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "itineraryForm:search")
                .data("rand", getRandom())
                .timeout(getTimeout())
                .post();

        if (doc.select(UPDATE).first().select(FLIGHT_SEARCH_ERROR).size() == 1)
            throw new TravelUsException(doc.select(UPDATE).first().select(CONTENT).text());

        log("4");
        doc = extractInnerHTML(doc, 0);
        log("5");

        for (Element availableFlight : doc.select(DATA_TABLE_ROW)) {

            Log.w("bc004346", String.format("%s\n\r\n\r", availableFlight.html()));

            String flightNumber = availableFlight.select("td[id$=:j_id106:0:j_id107-0-0]").text();
            log(flightNumber);
            Integer iFlightNumber = Integer.parseInt(flightNumber.substring(3));

            String departure = availableFlight.select("td[id$=:j_id116:0:j_id117-0-0]").text();
            log(departure);
            departureDate = new Date(departureDate.getTime() + SHORT_TIME.parse(departure).getTime());

            String arrival = availableFlight.select("td[id$=:j_id123:0:j_id124-0-0]").text();
            log(arrival);
            if (arrival.equals(""))
                arrival = availableFlight.select("td[id$=:j_id123:0:j_id127-0-0]").text();
            Date arrivalDate = new Date(departureDate.getTime() + SHORT_TIME.parse(arrival).getTime());
            if (arrivalDate.getTime() < departureDate.getTime())
                arrivalDate = new Date(arrivalDate.getTime() + DAY);

            String travelTime = availableFlight.select("td[id$=:j_id138-0-0]").text();
            log(travelTime);

            String equipment = availableFlight.select("td[id$=:j_id145-0-0]").text();
            log(equipment);

            String firstClass = availableFlight.select("td[id$=:j_id169:0:j_id174-0-0]").text();
            log(firstClass);
            Integer iFirstClass = Integer.parseInt(firstClass);

            String coach = availableFlight.select("td[id$=:j_id197-0-0]").text();
            log(coach);
            Integer iCoach = Integer.parseInt(coach);

            flights.add(new Flight(iFlightNumber, departureDate, origin,
                    destination, arrivalDate, travelTime, equipment, iFirstClass, iCoach));
        }

        return flights;
    }

    //</editor-fold>
    //<editor-fold desc="Private Methods">

    public List<Flight> pbtTracker() throws Exception {

        log("starting...");
        List<Flight> flights = new ArrayList<Flight>();

        Document doc = Jsoup.connect(URI_PBT_TRACKER).referrer(URI_ITINERARY)
                .cookie("JSESSIONID", sessionId).cookie("ice.sessions", "")
                .timeout(getTimeout()).get();

        if (doc.text().contains("Please enter your user name and password that you use for Wings")) {
            if (!doLogin(mUsername, mPassword))
                throw new TravelUsException("Unable to login");
            else {

                doc = Jsoup.connect(URI_PBT_TRACKER).referrer(URI_ITINERARY)
                        .cookie("JSESSIONID", sessionId).cookie("ice.sessions", "")
                        .timeout(getTimeout()).get();
            }
        }

        for (Element row : doc.select(DATA_TABLE_ROW)) {
            Integer flightNumber = Integer.valueOf(row.select("span[id$=:j_id46]").text().substring(3));
            String departurePoint = row.select("span[id$=j_id50]").text();
            String origin = departurePoint.substring(0, 3);
            Integer start = departurePoint.indexOf("(");
            Integer end = departurePoint.lastIndexOf(")");
            Date departureTime = SHORT_TIME.parse(departurePoint.substring(start + 1, end));
            String destination = row.select("span[id$=:j_id54]").text();
            Date departureDate = DATE.parse(row.select("span[id$=:j_id58]").text());
            Integer firstCapacity = Integer.valueOf(row.select("span[id$=:j_id62]").text());
            Integer firstSpaceAvailable = Integer.valueOf(row.select("a[id$=:j_id74]").text());
            Integer firstNonRev = Integer.valueOf(row.select("span[id$=:j_id78]").text());
            Integer coachCapacity = Integer.valueOf(row.select("span[id$=:j_id82]").text());
            Integer coachSpaceAvailable = Integer.valueOf(row.select("a[id$=:j_id94]").text());
            Integer coachNonRev = Integer.valueOf(row.select("span[id$=:j_id98]").text());
            departureDate = new Date(departureDate.getTime() + departureTime.getTime());
            flights.add(new Flight(flightNumber, origin, destination, departureDate, firstCapacity,
                    firstSpaceAvailable, firstNonRev, coachCapacity, coachSpaceAvailable, coachNonRev));
        }

        return flights;
    }

    public Flight checkNonRevList(Flight flight) throws Exception {

        log(String.format("start... (Flight = %s)", flight));

        Document doc = Jsoup.connect(URI_CHECK_PBT).referrer(URI_ITINERARY)
                .cookie("JSESSIONID", sessionId).cookie("ice.sessions", "")
                .timeout(getTimeout()).get();
        String session = doc.select(IFRAME).first().id().substring(14, 36);

        if (doc.text().contains("Please enter your user name and password that you use for Wings")) {
            if (!doLogin(mUsername, mPassword))
                throw new TravelUsException("Unable to login");
            else {

                doc = Jsoup.connect(URI_CHECK_PBT).referrer(URI_ITINERARY)
                        .cookie("JSESSIONID", sessionId).cookie("ice.sessions", "")
                        .timeout(getTimeout()).get();
                session = doc.select(IFRAME).first().id().substring(14, 36);
            }
        }

        doc = Jsoup.connect(URI_SEND_RECEIVE_UPDATES).referrer(URI_CHECK_PBT)
                .cookie("JSESSIONID", sessionId)
                .cookie("ice.sessions", session + "#1")
                .userAgent(USER_AGENT)
                .data("ice.submit.partial", "false")
                .data("ice.event.target", "checkPbtForm:j_id69:0:j_id103")
                .data("ice.event.captured", "checkPbtForm:j_id69:0:j_id103")
                .data("ice.event.type", "onclick")
                .data("ice.event.alt", "false")
                .data("ice.event.ctrl", "false")
                .data("ice.event.shift", "false")
                .data("ice.event.meta", "undefined")
                .data("ice.event.x", "279")
                .data("ice.event.y", "561")
                .data("ice.event.left", "false")
                .data("ice.event.right", "false")
                .data("checkPbtForm", "checkPbtForm")
                .data("icefacesCssUpdates", "")
                .data("javax.faces.ViewState", "1")
                .data("javax.faces.RenderKitId", "ICEfacesRenderKit")
                .data("checkPbtForm:flightNumber", String.valueOf(flight.getFlightNumber()))
                .data("checkPbtForm:board", flight.getOrigin())
                .data("checkPbtForm:board_idx", "")
                .data("checkPbtForm:dest", flight.getDestination())
                .data("checkPbtForm:dest_idx", "")
                .data("checkPbtForm:deptdate", SHORT_DATE.format(flight.getDeparture()))
                .data("checkPbtForm:_idcl", "checkPbtForm:j_id69:0:j_id103")
                .data("checkPbtForm:_idcheckPbtForm:deptdatesp", "")
                .data("ice.session", session)
                .data("ice.view", "1")
                .data("ice.focus", "checkPbtForm:j_id69:0:j_id103")
                .data("rand", getRandom())
                .timeout(getTimeout()).post();

        flight = parseNonRevList(flight, doc);

        log("end...");

        return flight;
    }

    private String getRandom() {
        return String.valueOf(Math.random());
    }

    private List<Passenger> getPassengers(Element table) {

        log("starting...");

        Element row;
        String name;
        String relationship;
        String ticket;

        List<Passenger> passengers = new ArrayList<Passenger>();
        for (Element element : table.select(DATA_TABLE_ROW)) {
            row = element;
            name = row.select(SPAN).get(0).text();
            relationship = row.select(SPAN).get(1).text();
            ticket = row.select(SPAN).get(2).text();

            passengers.add(new Passenger(name, relationship, ticket));
        }

        log(String.format("end... returning %d passengers", passengers.size()));

        return passengers;
    }

    private List<Flight> getFlights(Element table, String pnr) throws ParseException {

        log("starting...");

        List<Flight> flights = new ArrayList<Flight>();
        Element row;
        int flightNumber;
        Date departure;
        String origin;
        String destination;
        Date departureTime;
        Date arrival;

        for (Element element : table.select(DATA_TABLE_ROW)) {
            row = element;
            flightNumber = Integer.parseInt(row.select(SPAN).get(0).text());
            departure = LONG_DATE.parse(row.select(SPAN).get(1).text());
            origin = row.select(SPAN).get(2).text();
            destination = row.select(SPAN).get(3).text();
            departureTime = SHORT_TIME.parse(row.select(SPAN).get(4).text());
            arrival = SHORT_TIME.parse(row.select(SPAN).get(5).text());
            arrival = new Date(departure.getTime() + arrival.getTime() - new Date().getTimezoneOffset() * MINUTE);
            departure = new Date(departure.getTime() + departureTime.getTime() - new Date().getTimezoneOffset() * MINUTE);

            flights.add(new Flight(flightNumber, departure, origin,
                    destination, arrival, false, pnr, false));
        }

        log(String.format("end... returning %d flights", flights.size()));

        return flights;
    }

    private Flight parseAvailability(Flight flight, Document doc) {

        log("starting...");

        doc = extractInnerHTML(doc, 0);
        if (doc == null) {
            log("Unable to extract inner HTML");
            return flight;
        }

        Element table = doc.getElementById(CHECK_PBT_FORM_AVAILABILITY);
        for (Element row : table.select(PBT_DATA_TABLE_ROW)) {

            if (row.select(SPAN).get(1).text().contains(LABEL_FIRST)) continue;

            if (row.select(SPAN).get(0).text().contains(LABEL_TOTAL_CAPACITY)) {
                flight.getFirstClass().setCapacity(Integer.valueOf(row.select(SPAN).get(1).text().equals("") ? "0" : row.select(SPAN).get(1).text()));
                flight.getCoach().setCapacity(Integer.valueOf(row.select(SPAN).get(2).text().equals("") ? "0" : row.select(SPAN).get(2).text()));
                continue;
            }

            if (row.select(SPAN).get(0).text().contains(LABEL_AUTHORIZED)) continue;

            if (row.select(SPAN).get(0).text().contains(LABEL_BOOKED)) continue;

            if (row.select(SPAN).get(0).text().contains(LABEL_SPACE_AVAILABLE)) {
                flight.getFirstClass().setSeatsAvailable(Integer.valueOf(row.select(SPAN).get(1).text().equals("") ? "0" : row.select(SPAN).get(1).text()));
                flight.getCoach().setSeatsAvailable(Integer.valueOf(row.select(SPAN).get(2).text().equals("") ? "0" : row.select(SPAN).get(2).text()));
                continue;
            }

            if (row.select(SPAN).get(0).text().contains(LABEL_NON_REVS_LISTED)) {
                flight.getFirstClass().setNonRevsCount(Integer.valueOf(row.select(SPAN).get(1).text().equals("") ? "0" : row.select(SPAN).get(1).text()));
                flight.getCoach().setNonRevsCount(Integer.valueOf(row.select(SPAN).get(2).text().equals("") ? "0" : row.select(SPAN).get(2).text()));
            }
        }

        log(String.format("end... returning flight = %s", flight.toString()));

        return flight;
    }

    private Flight parseNonRevList(Flight flight, Document doc) {

        log("starting...");

        doc = extractInnerHTML(doc, 0);
        if (doc == null) {
            log("Unable to extract inner HTML");
            return flight;
        }

        Element table = doc.getElementById(CHECK_PBT_FORM_NON_REVS).getElementById(CHECK_PBT_FORM_NON_REVS);
        int position = 1;

        if (!table.text().contains(getName())) {
            flight.setBoardingPriority(0);
            return flight;
        }

        for (Element row : table.select(PBT_DATA_TABLE_ROW)) {

            if (row.select(SPAN).get(0).text().contains(LABEL_BOARDING_PRIORITY)) continue;

            if (row.select(SPAN).get(3).text().startsWith(getName()) && row.select(SPAN).get(2).text().equals(DATE.format(getSeniority()))) {
                flight.setBoardingPriority(position);
                break;
            } else {
                position += Integer.valueOf(row.select(SPAN).get(1).text());
            }
        }

        log(String.format("end... returning flight = %s", flight.toString()));

        return flight;
    }

    private Document extractInnerHTML(Document doc, int index) {

        log("1");
        int size = doc.select(CONTENT).size();
        if (size <= index) return null;

        log("2");
//        doc = Jsoup.parse(doc.select(CONTENT).get(index).html()
//                .replace("&lt;", "<")
//                .replace("&quot;", "\"")
//                .replace("&gt;", ">"));
        doc = Jsoup.parse(StringEscapeUtils.unescapeXml(doc.select(CONTENT).get(index).html()));
        log("3");
        return doc;
    }

    private void ParseUserData(Document doc) throws ParseException {
        String longName = doc.getElementById(FIELD_NAME).html().toUpperCase();
        longName = longName.substring(longName.lastIndexOf(' ') + 1);

        if (longName.length() < 5) longName += "\\";
        else longName = longName.substring(0, 5);

        setName(longName);
        setBadge(doc.getElementById(FIELD_BADGE).html());
        setSeniority(DATE.parse(doc.getElementById(FIELD_SENIORITY).html()));
    }

    //</editor-fold>
}
